﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestTcpSockets.Plugin.Interface;
using System.IO;
using TestTcpSockets.Xml;
using System.Text.RegularExpressions;

namespace TestTcpSockets.Plugin.System
{
    public class Authentication : INabPlugin
    {
        #region INabPlugin Members

        public bool HandleRequest(byte[] rawrequest, string request, global::System.IO.Stream reply)
        {
            throw new NotImplementedException();
        }

        #endregion

        public bool DoAuth(ref int currentAuthStep, string data, StringBuilder answer)
        {
           
            switch (currentAuthStep)
            {
                case 0:
                    {
                        if (data.StartsWith("<?xml version='1.0' encoding='UTF-8'?>"))
                        {
                            // Send an auth Request
                            answer.Append(
                                "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='2173750751' from='artiche.pck.nerim.net' version='1.0' xml:lang='en'>");
                            answer.Append(
                                "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms><register xmlns='http://violet.net/features/violet-register'/></stream:features>");
                            currentAuthStep = 1;
                            return true;
                        }
                    }
                    break;
                case 1:
                    {
                        // Bunny request a register <iq type='get' id='1'><query xmlns='violet:iq:register'/></iq>
                        IQ iq = IQ.Deserialize(data);
                        if (iq != null && iq.IqType == "get" && iq.Content != null &&
                            iq.Content.Namespace == "violet:iq:register")
                        {
                            // Send the request
                            /*
                                                answer = iq.Reply(IQ::Iq_Result, "from='" + xmpp->GetXmppDomain() + "' %1 %4", "<query xmlns='violet:iq:register'><instructions>Choose a username and password to register with this server</instructions><username/><password/></query>");
                                                xmpp->currentAuthStep = 100;
                                                return true;
                            */

                            //LogError("Authentication failure for bunny");
                            // Bad password, send failure and restart auth
                            answer.Append(
                                "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>");
                            currentAuthStep = 0;
                            return true;
                        }
                        // Bunny request an auth <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
                        if (data.StartsWith("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>"))
                        {
                            // Send a challenge
                            // <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>...</challenge>
                            // <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>nonce="random_number",qop="auth",charset=utf-8,algorithm=md5-sess</challenge>

                            Random random = new Random((int)(DateTime.Now.Ticks % Int16.MaxValue));

                            int nonce = random.Next();
                            

                            string challenge = "nonce=\"" + nonce + "\",qop=\"auth\",charset=utf-8,algorithm=md5-sess";

                            answer.Append("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + Convert.ToBase64String(Encoding.UTF8.GetBytes(challenge)) +
                                          "</challenge>");
                            currentAuthStep = 2;
                            return true;
                        }
                        //LogError("Bad Auth Step 1, disconnect");
                        return false;
                    }

                case 2:
                    {
                        // We should receive <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>...</response>
                        Regex rx = new Regex("<response[^>]*>(?<authString>.*)</response>");
                        Match match = rx.Match(data);
                        if (match.Captures.Count > 0)
                        {
                            string base64Data = match.Groups["authString"].Captures[0].Value;
                            if((base64Data.Length*6)%8 !=0)
                            {
                                int cpt = ((base64Data.Length * 6) % 8)/2;
                                for(int i=0;i<cpt;i++)
                                {
                                    base64Data += "=";
                                }
                            }
                            byte[] tmp = Convert.FromBase64String(base64Data);
                            string authString = Encoding.UTF8.GetString(tmp);
                            authString.Replace(((char) 0).ToString(), "");

                            // authString is like : username="",nonce="",cnonce="",nc=,qop=auth,digest-uri="",response=,charset=utf-8
                            rx =
                                new Regex(
                                    "username=\"(?<userName>[^\"]*)\",nonce=\"([^\"]*)\",cnonce=\"([^\"]*)\",nc=([^,]*),qop=auth,digest-uri=\"([^\"]*)\",response=([^,]*),charset=utf-8");
                            match = rx.Match(authString);
                            if (match.Captures.Count > 0)
                            {
                                string username = match.Groups["userName"].Captures[0].Value;

                                //Bunny * bunny = BunnyManager::GetBunny(username);

                                // Check if we want to bypass auth for this bunny
                                //if((GlobalSettings::Get("Config/StandAloneAuthBypass", false)) == true && (username == GlobalSettings::GetString("Config/StandAloneAuthBypassBunny")))
                                {
                                    // Send success
                                    //LogInfo("Sending success instead of password verification");
                                    answer.Append("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");

                                    //bunny->Authenticating();
                                    //*pBunny = bunny; // Auth OK, set current bunny

                                    currentAuthStep = 4;
                                    return true;
                                }


                                //QByteArray const password = bunny->GetBunnyPassword();

                                //// Check if password detection is activated :
                                //// - password is empty
                                //// - response is decoded by patched firmware
                                //if(password == "")
                                //{
                                //    rx.setPattern("response=([^:]*)::([^:]*):");
                                //    // If password is there, store it then
                                //    // authenticate the bunny (because patched firmware
                                //    // can't authenticate the bunny)
                                //    if(rx.indexIn(authString) != -1)
                                //    {
                                //        QByteArray passwordFound = rx.cap(2).toAscii();
                                //        bunny->SetBunnyPassword(passwordFound);
                                //        LogInfo("Storing new password for bunny");
                                //        bunny->Authenticating();
                                //        *pBunny = bunny; // Auth OK, set current bunny

                                //        xmpp->currentAuthStep = 4;
                                //        return true;
                                //    }
                                //    else
                                //    {
                                //        LogInfo("No password, and impossible to decode current data : disconnecting");
                                //        // Bad password, send failure and restart auth
                                //        answer.append("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>");
                                //        xmpp->currentAuthStep = 0;
                                //        return true;
                                //    }
                                //}

                                //QByteArray const nonce = rx.cap(2).toAscii();
                                //QByteArray const cnonce = rx.cap(3).toAscii().append((char)0); // cnonce have a dummy \0 at his end :(
                                //QByteArray const nc = rx.cap(4).toAscii();
                                //QByteArray const digest_uri = rx.cap(5).toAscii();
                                //QByteArray const bunnyResponse = rx.cap(6).toAscii();
                                //if(bunnyResponse == ComputeResponse(username, password, nonce, cnonce, nc, digest_uri, "AUTHENTICATE"))
                                //{
                                //    // Send challenge back
                                //    // <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>...</challenge>
                                //    // rspauth=...
                                //    QByteArray const rspAuth = "rspauth=" + ComputeResponse(username, password, nonce, cnonce, nc, digest_uri, "");
                                //    answer.append("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + rspAuth.toBase64() + "</challenge>");

                                //    bunny->Authenticating();
                                //    *pBunny = bunny; // Auth OK, set current bunny

                                //    xmpp->currentAuthStep = 3;
                                //    return true;
                                //}

                                //LogError("Authentication failure for bunny");
                                //// Bad password, send failure and restart auth
                                //answer.append("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>");
                                //xmpp->currentAuthStep = 0;
                                //return true;
                            }

                        }
                        //LogError("Bad Auth Step 2, disconnect");
                        return false;
                    }

                case 3:
                    {
                        // We should receive <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
                        if (data.StartsWith("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"))
                        {
                            // Send success
                            answer.Append("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
                            currentAuthStep = 4;
                            return true;
                        }
                        //LogError("Bad Auth Step 3, disconnect");
                        return false;
                    }

                case 4:
                    {
                        // We should receive <?xml version='1.0' encoding='UTF-8'?>
                        if (data.StartsWith("<?xml version='1.0' encoding='UTF-8'?>"))
                        {
                            // Send success
                            answer.Append(
                                "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='1331400675' from='artiche.pck.nerim.net' version='1.0' xml:lang='en'>");
                            answer.Append(
                                "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind><unbind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></stream:features>");
                            currentAuthStep = 5;
                            //(*pBunny)->Authenticated();
                            //(*pBunny)->SetXmppHandler(xmpp);
                            // Bunny is now authenticated
                            return true;
                        }
                        //LogError("Bad Auth Step 4, disconnect");
                        return false;
                    }

                case 100: // Register Bunny
                    {
                        // We should receive <iq to='artiche.pck.nerim.net' type='set' id='2'><query xmlns="violet:iq:register"><username>0019db01dbd7</username><password>208e6d83bfb2</password></query></iq>
                        IQ iqAuth = IQ.Deserialize(data);
                        if (iqAuth != null && iqAuth.IqType == "set")
                        {
                            //QByteArray content = iqAuth.Content();
                            //QRegExp rx("<query xmlns=\"violet:iq:register\"><username>([0-9a-f]*)</username><password>([0-9a-f]*)</password></query>");
                            //if(rx.indexIn(content) != -1)
                            //{
                            //    QByteArray user = rx.cap(1).toAscii();
                            //    QByteArray password = rx.cap(2).toAscii();
                            //    Bunny * bunny = BunnyManager::GetBunny(user);
                            //    if(bunny->SetBunnyPassword(ComputeXor(user,password)))
                            //    {
                            //        answer.append(iqAuth.Reply(IQ::Iq_Result, "%1 %2 %3 %4", content));
                            //        xmpp->currentAuthStep = 1;
                            //        return true;
                            //    }
                            //    LogError(QString("Password already set for bunny : ").append(QString(user)));
                            //    return false;
                            //}
                        }
                        //LogError("Bad Register, disconnect");
                        return false;
                    }

                default:
                    //LogError("Unknown Auth Step, disconnect");
                    return false;
            }
            return false;
        }
    }
}
